package uno.net;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import uno.Main;
import uno.component.Card;
import uno.component.CardValue;
import uno.component.ColorViewer;
import uno.component.Deck;
import uno.component.HandOfCard;
import uno.component.PlayedCardsList;
import uno.net.UnoThreadFactory.ReadyToPlayThread;
import uno.net.UnoThreadFactory.ReadyTokenState;
import uno.utilities.Configurator;
import uno.utilities.URNResolver;

public class UnoPlayer extends UnicastRemoteObject
							implements UnoPlayerRM {
	
	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = -319663021582070571L;
	/**
	 * Rappresenta un client di tipo normale (peer)
	 * */
	public static final boolean TYPE_NORMAL = true;
	/**
	 * Rappresenta un client di tipo leader
	 * */
	public static final boolean TYPE_LEADER = false;
	/** 
	 * get:set canIStartToPlay
	 * indica quando un giocatore ha ricevuto la lista dei player e può cominciare a giocare
	 */
	private boolean canIStartToPlay = false;
	public void setCanIStartToPlay(boolean value) {canIStartToPlay=value;}
	public boolean getCanIStartToPlay() {return canIStartToPlay;}
	/** 
	 * get:set playerName
	 * nome del giocatore
	 */
	private String playerName;
	public void setPlayerName(String name) {playerName=name;}
	public String getPlayerName() {return playerName;}
	/**
	 * get:set playerPort
	 * porta RMI registry del player
	 */
	private int playerPort;
	public void setPlayerPort(int port) {playerPort=port;}
	public int getPlayerPort() {return playerPort;}
	/**
	 * get:set playersList
	 * otherPlayers: lista contenente indirizzo e hostname degli altri giocatori
	 */	
	private LinkedList<UnoPlayerID> playersList;
	public LinkedList<UnoPlayerID> getPlayersList() { return playersList; }
	public void setPlayersList(LinkedList<UnoPlayerID> playersList) { this.playersList = playersList; }
	/**
	 * Mazzo globale, per ora lo metto qui..
	 * */
	private Deck deck;
	public Deck getDeck() { return deck; }
	public void setDeck(Deck givenDeck) { deck = givenDeck; }
	/**
	 * Mano di carte del giocatore.
	 */
	private HandOfCard hoc;
	public HandOfCard getHandOfCard() { return hoc; }
	public void setHandOfCard(HandOfCard givenHoc) { hoc = givenHoc; }
	/**
	 * Indirizzo ip del server
	 * */
	private String serverAddress;
	public String getServerAddress() { return serverAddress; }
	public void setServerAddress(String serverAddress) { this.serverAddress = serverAddress; }
	/**
	 * Thread Invoker: invoca i thread id UNO
	 * get:set Thread Invoker
	 */
	private UnoThreadFactory threadFactory;
	private void setThreadFactory(UnoThreadFactory givenTF) { threadFactory = givenTF; }
	private UnoThreadFactory getThreadFactory() { return threadFactory;}
	/**
	 * Gestore del file di configurazione
	 * */
	private Configurator config;
	private void setConfigurator(Configurator givenCFG) { config = givenCFG; }
	private Configurator getConfigurator() { return config; }
	/**
	 * Reference all'oggetto gui
	 * */
	private Main parent;
	public void setParent(Main givenM) { parent = givenM; }
	public Main getParent() { return parent; }	
	/**
	 * Leader
	 */
	private UnoPlayerID currentLeaderId;
	public UnoPlayerID getCurrentLeaderID() { return currentLeaderId; }
	public synchronized void setCurrentLeaderID(UnoPlayerID givenLI) { currentLeaderId = givenLI; } 
	/**
	 * Lista rappresentante le carte giocate
	 * */
	private PlayedCardsList playedCardsList;
	public PlayedCardsList getPlayedCardsList() { return playedCardsList; }
	/**
	 * Stabilisce il senso di giro nell'anello
	 * */
	private boolean flow;
	public void changeFlow() { this.flow = (!(this.flow)); }
	public void setFlow() { this.flow = true; }
	/**
	 * Oggeto che lancia thread per la gestione del token ready
	 * */
	ReadyToPlayThread cardsDistribuitionThread;
	public ReadyToPlayThread getCardsDistribuitionThread() { return cardsDistribuitionThread; }
	public void setCardsDistribuitionThread(ReadyToPlayThread cardDistribuitionThread) { this.cardsDistribuitionThread = cardDistribuitionThread; }
	/**
	 * restituisce lo stato del gioco, ovvero l'id dell'ultima giocata
	 * effettuata o ricevuta
	 */
	private UnoPlay gameStatus = new UnoPlay(0,null,null,0,0);  
	public UnoPlay getGameStatus() { return this.gameStatus; }
	
	
	
	/**
	 * Costruttore
	 * 
	 * @param parent contenitore grafico di tipo Main
	 * */
	public UnoPlayer(String name, Main parent) throws RemoteException {
		
		this.setParent(parent);
		// inizializza il configurator
		this.setConfigurator(new Configurator(Configurator.TYPE_CLIENT));
		// assegna il nome del giocatore tipicamente è l'hostname
		this.setPlayerName(name);
		// assegna la porta RMI
		this.setPlayerPort(Integer.parseInt(this.getConfigurator().getRmiPort()));
		// assegna l'indirizzo del server dal file di configurazione
		this.setServerAddress(this.getConfigurator().getServerIp() + ":" + this.getConfigurator().getServerPort());
		// inizializza il mazzo di carte
		this.setDeck(null);
		// inizializza la mano del giocatore
		this.setHandOfCard(null);
		// inizializza la thread factory
		this.setThreadFactory(new UnoThreadFactory(this));
		// inizializzo il leader
		this.setCurrentLeaderID(null);
		// inizializzo la direzione del gioco in senso orario
		this.setFlow();
		//inizializzo l'oggetto del thread token ready
		this.setCardsDistribuitionThread(getThreadFactory().createReadyTokenThread());
	}
	
	////////////////////////////////////////////////
	// inzio delle implementazione dei metodi remoti
	////////////////////////////////////////////////
	
	/**
	 * richista di propagazione dello stato forzata
	 */
	public boolean forcePropagation(int playID) throws RemoteException {
		boolean isStatusConsistent = true;
		// controllo l'id della propagazione della giocata richiesta
		if(this.gameStatus.getID() > playID) {
			// se l'id del mio stato e' maggiore, allora ripropago lo stato
			propagateStatus(this.gameStatus); 
			isStatusConsistent = false;
		} else if((this.gameStatus.getID() == playID) && amICurrentLeader()) {
			// se sono il leader e mi arriva una richiesta di propagazione
			// significa che la mia giocata e' arrivata al candidato leader che pero'
			// ha avuto un guasto, il secondo candidato mi chiede la propagazione
			// dello stato, quindi siamo consistenti.
			// quindi chiudo il giro
			this.updateGameStatus(this.gameStatus);
		}
		return isStatusConsistent;
	}

	/**
	 *Metodo remoto che consente di
	 *ricevere il mazzo globale.
	 */
	public void putDeck(ArrayList<String> urnList) {
		this.setDeck(new Deck(urnList, this.getParent()));	
		System.out.println("*[LOG] mi hanno consegnato lo stato iniziale del mazzo");
	}
	
	/**
	 *Metodo remoto che consente di ricevere la mano
	 * di carte.
	 */
	public void putHandOfCards(final ArrayList<String> urnList) {
		setHandOfCard(new HandOfCard(this));
		for(Iterator<String> i = urnList.iterator(); i.hasNext(); ) {		
			getHandOfCard().addCard(URNResolver.URNToCard(i.next(), getHandOfCard()));			
		}	
		System.out.println("*[LOG] mi hanno consegnato la mano");	
	}

	/** 
	 * Riceve la lista dei giocatori che si sono iscritti al server per cominciare il gioco
	 */
	public boolean getPlayersList(LinkedList<UnoPlayerID> playersList)
	throws RemoteException {
		boolean received = true;
		if(playersList.size() > 0) {
			this.setPlayersList(playersList);
			this.getParent().createAndSetPlayersList(playersList);
			System.out.println("*[LOG] tabella dei giocatori ricevuta correttamente");
		} else {
			received = false;
			System.err.println("*[WARNING] tabella dei giocatori vuota");
		}
		this.setCanIStartToPlay(true);
		return received;
	}
	
	/**
	 * Metodo che controlla se un giocatore e' vivo
	 * */
	public void areYouAlive() throws RemoteException { }
	
	
	/**
	 * Metodo che passa l'update del gioco dopo aver effettuato una mossa
	 * Viene richiamato inizialmente da chi ha fatto la mossa tramite il thread updateStatus
	 * che viene lanciato dal metodo propagateStatus
	 * 
	 * possibilità di aggiornamenti di stato ravvicinati.
	 * updateGameStatus diventa una critical section, quindi synchronized
	 * */
	public synchronized void updateGameStatus(UnoPlay play) throws RemoteException {
		// controllo se sono il leader corrente
		if (!(amICurrentLeader())) {
			// se non lo sono...
			// controllo l'id della giocata
			if(play.getID() > this.gameStatus.getID()) {
				// se l'id della giocata è superiore a quello locale...
				// significa che il leader corrente ha giocato
				System.out.println("*[LOG] il leader ha giocato");
				// termino il vecchio thread AreYouAlive...
				this.getThreadFactory().getAreYouAliveThread().kill();
				System.out.println("*[LOG] ricezione stato globale del gioco...");
				// e aggiorno lo stato del gioco
				updateStatus(play);
			} else if ((play.getID() == this.gameStatus.getID()) && (!(this.idEquals(play.getOwnerId())))) {
				// se l'id della giocata è uguale a quello dello stato locale
				// e se non sono l'owner della mossa
				// significa che il leader ha ripropagato lo stato...
				System.out.println("*[LOG] il leader ha ripropagato lo stato del gioco: ID: " + play.getID());
				// quindi effettuo nuovamente la propagazione
				this.propagateStatus(play);
			}
			// controllo se il player che ha appena giocato ha finito le carte
			if(play.getRemainingCards() == 0) {
				JOptionPane.showMessageDialog(getParent(), play.getOwnerId().getName() + " ha vinto!");
				System.exit(0);
			}
			
		} else {
			// sono il leader corrente...
			// controllo che la mossa effettuata sia effettivamente la mia...
			if((play.getID() == this.gameStatus.getID()) && (this.idEquals(play.getOwnerId()))) {
				// se l'id della giocata è uguale a quello locale
				// e l'owner della giocata corrisponde all'id del peer locale
				// significa che il token ha effettuato il giro completo dell'anello quindi...
				System.out.println("*[LOG] lo stato del gioco ha effettuato un giro completo");
				// è necessario terminare il thread ripetitore...
				getThreadFactory().getUpdateGameStatusRepeaterThread().kill();
				System.out.println("*[LOG] terminato il thread di propagazione ripetuta dello stato del gioco");
				// e aggiornare lo stato della leadership
				updateLeadershipStatus();
				
			} else if((play.getID() > this.gameStatus.getID()) && (!(this.idEquals(play.getOwnerId())))) {
				// so di essere gia' il leader perche' il leader corrente ha avuto un guasto
				// termino l'attesa dell'update aggiornando lo stato e preparandomi al turno di gioco
				updateLocalStatus(play);
				
				// FIXED
				// setto il leader corrente a quello della giocata effettuata
				// setCurrentLeaderID(play.getOwnerId());
				// ricalcolo la leadership
				// updateLeadershipStatus();
			}
			if(amICurrentLeader()) setEnabledGUI(true);
			// NB (leader o normal peer): altrimenti se l'id della giocata è minore dello stato locale 
			// non faccio nulla
			// è inutile propagare un vecchio stato
		}	
	}

	/**
	 * Invia il token ready.
	 * Questo metodo serve per confermare a tutti che le carte sono state
	 * ricevute correttamente da tutti i giocatori.
	 * 
	 */
	public void sendReadyToken() {
		// controllo lo stato del token, se e' necessario inviarne uno nuovo allora lo invio altrimenti niente.
		if(!getCardsDistribuitionThread().manageTokens())
			SwingUtilities.invokeLater(getCardsDistribuitionThread());
		//getCardsDistribuitionThread().start();
	}

	///////////////////////////////////////////////
	// fine delle implementazioni dei metodi remoti
	///////////////////////////////////////////////
	
	/**
	 * Metodo che aggiorna lo stato del gioco locale e spedisce l'update
	 * anche al giocatore successivo nella lista
	 * */
	public void updateStatus(UnoPlay play) {		
		// AGGIORNAMENTO LOCALE DELLO STATO DEL GIOCO
		// prima di tutto aggiorno lo stato locale del gioco, quindi...
		// aggiungo la carta alla lista delle carte giocate e setto a null il valore
		// della mano che lo contiene, aggiorno il mazzo
		this.updateLocalStatus(play);
		
		// PROPAGAZIONE DELLO STATO DEL GIOCO
		// propago il token relativo allo stato del gioco mediate il ripetitore se sono il leader
		// altrimenti mediante la propagateStatus
		// il ripetitore lancia propagateStatus(play) ciclicamente mediante un timeout
		// una volta inoltrato correttamente lo stato calcolo la leadership
		// il leader la calcola solo una volta che il token ha concluso il giro completo dell'anello
		// quindi...
		// controllo se sono il leader corrente
		if (this.amICurrentLeader()) {
			// se lo sono propago lo stato mediante il ripetitore
			Thread thread = this.getThreadFactory().createUpdateGameStatusRepeaterThread(play);
			thread.start();

			// controllo se ho finito le carte, ossia se ho vinto il gioco
			if(checkEndOfGame()){
				System.out.println("[Info] Ho finito le carte e quindi ho finito di giocare.");
				JOptionPane.showMessageDialog(getParent(), "Sei un figo!");
				System.exit(0);
			}
		} else { 
			// altrimenti mediante le modalità canoniche
			this.propagateStatus(play); 
		}
	}
	
	/**
	 * Controlla se le carte della mano sono finite
	 * */
	private boolean checkEndOfGame(){
		boolean endFlag = false;
		System.out.println(getHandOfCard().getHOCSize());
		if(getHandOfCard().getHOCSize() == 0)
			endFlag = true;
			
		return endFlag;
	}
	
	/**
	 * Calcola la leadership e avvia il thread AreYouAlive sul nuovo leader 
	 */
	public synchronized void updateLeadershipStatus() {
		// calcolo lo stato della leadership
		if(!(this.changeAndCheckLeadership())) {
			// creo il nuovo thread AreYouAlive da invocare verso il nuovo leader
			this.getThreadFactory().createAreYouAliveThread(this.getCurrentLeaderID()).start();
		} else {
			System.out.println("*[LOG] il leader ora sono io");
		}
	}
	
	/**
	 * Aggiorna lo stato locale del gioco
	 * @param play
	 */
	public void updateLocalStatus(final UnoPlay play) {
		// aggiorno lo stato locale del gioco
		this.gameStatus = play;
		System.out.println("*[LOG] aggiornamento stato locale del gioco: ID: " + this.gameStatus);
		// se non sono il leader corrente (colui che ha fatto la giocata)...
		// allora aggiorno lo stato del mazzo
		
		//FIXED
		//if(!this.amICurrentLeader()) {
		if(!idEquals(play.getOwnerId())) {
			updateDeckStatus(play); 
		} else {
			// mentre se sono il leader...
			// disabilito l'interfaccia dato che auspicabilmente il turno successivo
			// sarà di un altro peer
			this.setEnabledGUI(false);
			// NB: se per qualche motivo il prossimo leader sono sempre io
			// l'interfaccia viene riabilitata in updateLeadershipStatus
		}
		//--------------------------------
		// controlla che nell'ultima mano sia stata giocata una carta...
		if (play.getCardPlayed() != null) {
			// se è stata giocata la carta...
			// aggiungo la carta giocata alla lista delle carte giocate
			getPlayedCardsList().addCard(URNResolver.URNToCard(play.getCardPlayed(), null));
			// aggiorna il colore in gioco 
			final int cardPlayedType = getPlayedCardsList().getLastPlayedCard().getValue().getType();
			// se e' una carta cambia colore o +4 allora devo verificare il colore scelto 
			if(cardPlayedType == CardValue.CHANGE_COLOR || cardPlayedType == CardValue.PLUS_FOUR) {
				// faccio scegliere il colore al giocatore
				getParent().getColorViewer().setCurrentColor(play.getChoosedColor());
				Main.globalCurrentColor = play.getChoosedColor();

				// controllo che l'ultima carta giocata sia +4
				if(cardPlayedType == CardValue.PLUS_FOUR) {
					// se è +4...
					// controllo se sono il prossimo leader e quindi il prossimo a giocare...
					if(this.idEquals(this.getNextLeaderID())) {
						// se lo sono allora aggiungo alla mia mano 4 carte
						for(int i=0; i<4; i++)
							getDeck().getLastCard();
					} else {
						// se non sono il leader dovrei togliere le 4 carte dal mazzo?
						for(int i=0; i<4; i++)
							getDeck().pollLastCard();
					}
				}
				
			} else {
				// altrimenti lo chiedo alla carta
				String color = getPlayedCardsList().getLastPlayedCard().getValue().getColor();
				// perché ne aggiorniamo due?
				// non potremmo fare che una di queste dipenda dall'altra?
				getParent().getColorViewer().setCurrentColor(color);
				Main.globalCurrentColor = color;

				// controllo di che tipo è la carta giocata 
				switch(cardPlayedType){
				case CardValue.CHANGE_FLOW:
					this.changeFlow();
					break;
				case CardValue.PLUS_TWO:
					// controllo se sono il prossimo leader e quindi il prossimo a giocare...
					if(this.idEquals(this.getNextLeaderID())) {
						// se lo sono allora aggiungo alla mia mano 4 carte
						for(int i=0; i<2; i++)
							getDeck().getLastCard();
					} else {
						for(int i=0; i<2; i++)
							getDeck().pollLastCard();					
					}
					break;
				}
			}
			// aggiorna la lista dei giocatori grafica (il campo relativo al numero di carte di ogni giocatore)
			SwingUtilities.invokeLater(new Runnable(){
				public void run() {
					if(cardPlayedType == CardValue.PLUS_TWO) {
						getParent().getPlayersList().updatePlayerStatus(getPlayersList().indexOf(getNextLeaderID()), -2);
					} else if(cardPlayedType == CardValue.PLUS_FOUR){
						getParent().getPlayersList().updatePlayerStatus(getPlayersList().indexOf(getNextLeaderID()), -4);
					}
					// aggiorno le carte di chi ha appena giocato.
					getParent().getPlayersList().updatePlayerStatus(getPlayersList().indexOf(getCurrentLeaderID()), play.getRemainingCards());
					// Aggiorna la lista grafica
					getParent().validate();
				}});
			
		} else {
			
			// aggiorna il numero di carte in caso sia stato effettuato un pass MA si ha prelevato una carta dal mazzo
			SwingUtilities.invokeLater(new Runnable(){
				public void run() {
					getParent().getPlayersList().updatePlayerStatus(getPlayersList().indexOf(getCurrentLeaderID()), play.getRemainingCards());
					// Aggiorna la lista grafica
					getParent().validate();
				}
			});
		}
		
	}
	
	/**
	 * Invia il nuovo stato al peer successivo e una volta andata a buon fine la
	 * propagazione aggiorna la leadership 
	 * */
	public void propagateStatus(UnoPlay play) {
		// calcolo il prossimo peer della lista e a questo propago la giocata 
		UnoPlayerID nextPeerID = this.getNextPeerID();
		System.out.println("*[LOG] propagazione stato globale del gioco...");
		Thread thread = this.getThreadFactory().createUpdateGameStatusThread(nextPeerID, play);
		thread.start();
	}
	
	/**
	 * Aggiorna lo stato del mazzo
	 * */
	private void updateDeckStatus(UnoPlay play) {
		getDeck().pollCardsFromParamToEnd(play.getActualDeckSize());
	}
	
	/**
	 * Abilita/Disabilita l'interfaccia.( il mazzo coperto e la mano del giocatore ) 
	 * */
	public void setEnabledGUI(boolean flag) {
		try {
		// abilito il click sul mazzo
		this.getDeck().setEnableClickOnDeck(flag);
		// abilito il click sulla mano 
		this.getHandOfCard().setEnableClickListener(flag);
		// abilito il menu dei comandi
		this.getParent().getUnoMenuBar().setEnablePlayCommands(flag);
		} catch (NullPointerException npe) {
			System.err.println("*[INFO/WARNING] il mazzo, la mano sono null, abilitazione fallita");
		}
	}
	
	/**
	 * Verifica se sono il leader o meno.
	 * */
	public boolean amICurrentLeader() { return this.idEquals(this.getCurrentLeaderID()); }
		
	/**
	 * Cambia la leadership al player successivo e verifica se e' il giocatore che ha chiamato il 
	 * metodo ad aver ottenuto la leadership
	 * */
	public synchronized boolean changeAndCheckLeadership() {
		// controllo che la lista dei giocatori esista		 	
		if(getPlayersList() != null) {
			// controllo che ci sia stato un leader nel turno precedente
			if(getCurrentLeaderID() == null) {
				// se l'ID del leader è null, siamo nel primo turno di gioco
				// il leader è il primo della lista
				this.setCurrentLeaderID(getPlayersList().getFirst());
			} else {
				// non siamo nel primo turno di gioco, devo calcolare il prossimo leader
				this.setCurrentLeaderID(this.getNextLeaderID());
			}	
			// nella lista dei giocatori evidenzia graficamente il leader corrent,
			// ovvero colui che ha il turno di gioco
			SwingUtilities.invokeLater(new Runnable(){
				public void run() {
					getParent().getPlayersList().setLeadership(getPlayersList().indexOf(getCurrentLeaderID()));
				}});
			for(int i = 0; i < this.getPlayersList().size(); i++) {
				if(idEquals(this.getPlayersList().get(i), getCurrentLeaderID()))
					getParent().getPlayersList().setSelectedIndex(i);
			}			
		} else System.out.println("*[WARNING] tabella dei giocatori vuota");
		
		return this.idEquals(this.getCurrentLeaderID());
	}
	
	/**
	 * Rimuove il player passato come parametro dalla lista dei giocatori
	 * integra anche la funzionalità grafica
	 * 
	 * @param playerID
	 */
	public synchronized void removePlayerFromList(UnoPlayerID playerID, boolean isInvokedByLeaderCrashTollerance) {
		boolean isStatusConsistent = true;
		UnoPlayerID nextPeerID = null;
		UnoPlayerRM nextPeer = null;
		if(idEquals(getCurrentLeaderID(), playerID) && isInvokedByLeaderCrashTollerance) {
			// termino il thread di attesa del leader corrente [NB per ora meglio commentarla]
			// getThreadFactory().getAreYouAliveThread().kill();
			// calcolo il nuovo leader
			updateLeadershipStatus();
			// controllo se sono il nuovo leader
			if(amICurrentLeader()) {
				// se lo sono controllo che mi sia arrivato l'ultimo stato
				// se non mi e' arrivato richiedo una propagazione forzata
				if(!((getCardsDistribuitionThread().getReadyTokenState() == ReadyTokenState.NOT_SENT) ||
						(getDeck() == null || getHandOfCard() == null))) {
					while(true) {
						try {
							nextPeerID = getNextPeerID();
							nextPeer = UnoPlayer.playerIDToPlayer(nextPeerID);
							System.err.println("* [INFO] lancio una forcePropagation su " + nextPeerID);
							if(nextPeer == null) throw new RemoteException(); 
							isStatusConsistent = nextPeer.forcePropagation(this.gameStatus.getID());
							if(isStatusConsistent) {	
								// abilito l'interfaccia per giocare
								setEnabledGUI(true);
								System.out.println("* [INFO] lo stato e' consistente, abilito la gui");  System.out.flush();
								break;	
							} else Thread.sleep(2000);	
						} catch (RemoteException e) {
							System.err.println("*[INFO] il nextPeer e' morto, lo rimuovo");
							removePlayerFromList(nextPeerID, false);
							e.printStackTrace();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
		if(isInvokedByLeaderCrashTollerance || !(idEquals(getCurrentLeaderID(), playerID))) {
			int cur = -1;
			// individua il peer che ha avuto un guasto
			for(int i = 0; i < this.getPlayersList().size(); i++) 
				if(idEquals(this.getPlayersList().get(i), playerID)) { cur = i; }
			// controllo di averlo individuato
			if(!(cur < 0)) {
				// se è stato individuato...
				// rimuove il peer dalla lista
				this.getPlayersList().remove(cur);
				System.err.println("*[INFO] removePlayerFromList eseguito");
				// aggiorna la lista grafica
				final int indexToBeRemoved = cur;
				SwingUtilities.invokeLater(new Runnable(){
					public void run() {
						getParent().getPlayersList().removePlayerFromList(indexToBeRemoved);
					}});

			}

			// controllo di non essere rimasto da solo
			if(getPlayersList().size() == 1){
				try{
					getThreadFactory().getUpdateGameStatusRepeaterThread().kill();
				} catch(Exception e){
					System.err.println("*[WARNING]: fallito il kill del thread update repeater. Causa: " + e.getMessage());
				}
				JOptionPane.showMessageDialog(getParent(), "Sono morti tutti! \nSei rimasto solo al mondo..\npero' hai vinto a tavolino!");
				System.exit(0);
			}
		}
	}
	
	/**
	 * Calcola l'id del prossimo giocatore: leader o normale 
	 */ 
	private UnoPlayerID getNextPlayerID(boolean type) {
		int index = 0;
		
		// type = true -> cerca il prossimo normal peer nella lista / direzione "anti-gioco"
		// type = false -> cerca il prossimo leader peer / direzione gioco
		UnoPlayerID currID = type ? this.getMyPlayerID() : this.getCurrentLeaderID();
		// se si cerca il prossimo peer per la propagazione dello stato devo andare in senso "anti-gioco"
		// se si cerca il prossimo leader devo andare in senso del gioco
		boolean direction = type ? (!(this.flow)) : this.flow;
		
		// MEMENTO: utilizzo un ciclo for perché, inspiegabilmente, indexOf restituisce -1
		// this.getPlayersList().indexOf(currID);
		for(int i = 0; i < this.getPlayersList().size(); i++) 
			if(idEquals(this.getPlayersList().get(i), currID)) { index = i; } 
		
		// controlla la direzione di ricerca del peer
		// il valore di "direction" dipende dalla direzione del gioco e dal tipo di peer ricercato
		if(direction) {	
			// il turno del gioco va assegnato al successivo in senso orario
			// oppure scorriamo la lista dei giocatori in senso orario (se la direzione del gioco è anti-oraria)
			index = (index == this.getPlayersList().size() - 1) ? 0 : (index + 1);
			if(this.getPlayedCardsList() != null)
				// se sto cercando il prossimo leader, la direzione è oraria e la carta giocata è "salta il turno" allora incremento ancora di 1
				if((!(type)) && (this.getPlayedCardsList().getLastPlayedCard().getValue().getType() == CardValue.ONE_STOP)) { 
					index = (index == this.getPlayersList().size() - 1) ? 0 : (index + 1);
				}
		} else {
			// il turno del gioco va assegnato al successivo in senso anti-orario
			// oppure scorriamo la lista dei giocatori in senso anti-orario (se la direzione del gioco è oraria)
			index = (index == 0) ? (this.getPlayersList().size() - 1) : (index - 1);
			if(this.getPlayedCardsList() != null)
				// se sto cercando il prossimo leader, la direzione è anti-oraria e la carta giocata è "salta il turno" allora decremento ancora di 1
				if((!(type)) && (this.getPlayedCardsList().getLastPlayedCard().getValue().getType() == CardValue.ONE_STOP)) { 
					index = (index == 0) ? (this.getPlayersList().size() - 1) : (index - 1);
				}
		}
		if(type) System.out.println("*[LOG] calcolato il prossimo peer: list index: " + index);
		else System.out.println("*[LOG] calcolato il prossimo leader: list index: " + index);
		return this.getPlayersList().get(index);
	}
	
	/**
	 * Ritorna l'ID del prossimo peer.
	 * */
	public UnoPlayerID getNextPeerID() { return this.getNextPlayerID(UnoPlayer.TYPE_NORMAL); }
	
	/**
	 * Ritorna l'ID del prossimo leader.
	 * */
	public UnoPlayerID getNextLeaderID() { return this.getNextPlayerID(UnoPlayer.TYPE_LEADER); }
	
	/** 
	 * Avvia il gioco
	 */
	public void startsToPlay() {
		System.out.println("*[LOG] inizio a giocare...");		
		if(changeAndCheckLeadership()) {
			System.out.println("*[LOG] sono il leader");
			//setto il mazzo e mescolo le carte
			this.setDeck(new Deck(this.getParent()));
			this.getDeck().shuffleCards();
			// prendo la mia mano
			this.setHandOfCard(this.getDeck().getHOF(this));
			JOptionPane.showMessageDialog(getParent(), "inizio la consegna delle carte?");
			// consegno la mano e il mazzo agli altri
			giveHOFtoAll();
			giveDeckToAll();
			// lancio il  thread che gestisce il token ready 
			sendReadyToken();
		} else {
			// attendo il leader...
			System.out.println("*[LOG] attendo la mossa del leader...");
			this.getThreadFactory().createAreYouAliveThread(getCurrentLeaderID()).start();
		}
	}
	
	/**
	 * Visualizza il mazzo coperto, la prima carta scoperta, la mano e il colore della prima
	 * carta scoperta nella GUI.
	 * */
	public void showAllInGUI() {
		// aggiungo alla visualizzazione grafica il mazzo
		this.getParent().addDeck(this.getDeck());
		this.getDeck().revalidate();
		this.getDeck().setHidden(false);
		// aggiungo alla visualizzazione grafica la mano
		this.getParent().addHof(this.getHandOfCard());
		this.getHandOfCard().revalidate();
		// prendo la prima carta normale e "la butto sul tavolo" togliendola dal mazzo
		// lo faccio dopo aver consegnato il mazzo cosi' da poterlo far fare agli altri
		// peer senza passare ulteriori informazioni
		try {
			initPlayedCardsList();
		} catch (Exception e) { System.err.println("*[WARNING] non ci sono carte normali (non jolly) nel mazzo"); }
		//visualizzo il colore nel visualizzatore
		setCurrentColor(getPlayedCardsList().getLastPlayedCard());
	}
	
	/**
	 * Inizializza la lista di carte giocate
	 * @throws Exception 
	 * */
	private void initPlayedCardsList() throws Exception {		
		Card cardTmp = deck.getCard(deck.getDeckSize() - 1);
		
		int k = deck.getDeckSize() - 1;
		while( cardTmp.getValue().getType() != CardValue.NO_EFFECT && k > 0) {			
			k--;
			cardTmp = deck.getCard(k);			
		}
		// PS Il test sottostante e' ragionevolmente inutile, in quanto, questo metodo
		// dovrebbe essere lanciato solo all'inizio della partita
		// e quindi non dovrebbe verificarsi l'assenza di una carta non jollie
		// in ogni caso, per ora, lo lascerei
		
		// se non trovo nessuna carta (escluso i jollie)
		if(k < 0) {
			throw new Exception();		
		} else {// se ne ho trovata una che non sia una carta jollie la metto sul tavolo
			// togliendola dal mazzo
			deck.pollCard(k);
			playedCardsList = new PlayedCardsList(cardTmp);			
			// la setto anche nella grafica
			this.getParent().setPlayedCardsList(playedCardsList);
		}
	}
	
	/**
	 * Setta il colore in gioco nel visualizzatore di colori
	 * */
	private void setCurrentColor(Card lastPlayedCard) {		
		getParent().setColorViewer(new ColorViewer(lastPlayedCard.getValue().getColor()));
		Main.globalCurrentColor = lastPlayedCard.getValue().getColor();
	}
	
	/**
	 * Controlla se la chiave ( ip:port ) che mi identifica e' uguale
	 * a quella passata come parametro
	 * */
	public boolean idEquals(UnoPlayerID id) { return this.getMyPlayerID().toString().equals(id.toString()); }
	
	/**
	 * Controlla se i due ID passati come parametro sono uguali.
	 * */
	public static boolean idEquals(UnoPlayerID id1, UnoPlayerID id2) { return id1.toString().equals(id2.toString()); }
	
	/**
	 * Consegna la mano a tutti i giocatori.
	 * 
	 * NB: Deve essere richiamato dopo aver creato il mazzo ( e averlo
	 * mescolato ).
	 * */
	private void giveHOFtoAll() {

		System.out.println("*[LOG][leader] consegno a tutti la propria mano...");
		/*
		 * Fatta una mano la spedisco 
		 * scorrendo la lista al contrario (descendingIterator)
		 * */
		for(Iterator<UnoPlayerID> i = getPlayersList().descendingIterator(); i.hasNext(); ) {
			UnoPlayerID playerId = i.next();
			if(!idEquals(playerId)) {
				/* salto me stesso 
				 * passo null come parent perche' tanto questa mano
				 * viene trasformata in una lista di urn e poi
				 * riconvertita dal player che la riceve,
				 * il quale avra' cura di settare il parent corretto.*/
				HandOfCard currentHof = this.getDeck().getHOF(null);
				ArrayList<String> urnList = currentHof.toURNList();

				UnoPlayerRM UP;
				try {
					UP = playerIDToPlayer(playerId);
					UP.putHandOfCards(urnList);
				} catch (RemoteException re) {
					System.err.println("*[INFO] un giocatore e' crashato durante la consegna dele mani causa: RemoteException");
				} catch (Exception e) {
					System.err.print("*[INFO] un giocatore e' crashato durante la consegna delle mani");
				}		
			}
		}
	}
	
	/**
	 * Consegna il mazzo a tutti gli altri giocatori.
	 * 
	 * Se chiamato all'avvio di una partita deve essere chiamato 
	 * DOPO aver fatto la mano di tutti i giocatori.
	 * */
	private void giveDeckToAll() {
		System.out.println("*[LOG][leader] consegno a tutti lo stato del mazzo...");
		//distribuisco il mazzo(informo sullo stato)
		for(Iterator<UnoPlayerID> i = getPlayersList().descendingIterator(); i.hasNext(); ) {		
			UnoPlayerID playerId = i.next();			
			ArrayList<String> deckOfUrn = this.getDeck().toURNList();			 
			// verifico di non spedirlo a me
			if(!idEquals(playerId)) {
				UnoPlayerRM UP;
				try {
					UP = playerIDToPlayer(playerId);
					UP.putDeck(deckOfUrn);
				} catch (RemoteException re) {
					System.err.println("[INFO] un peer ha avuto un guasto durante la consegna dei mazzi, causa: RemoteException");
				} catch (Exception e) {
					System.err.print("[INFO] un peer ha avuto un guasto durante la consegna dei mazzi");
				}
			}
		}	
	}
	
	/**
	 * Iscrive il client al server
	 * */
	public boolean SignMeIn() {
		String remoteService = "rmi://" + getServerAddress() + "/unoServerRM";
		System.out.println("*[LOG] iscrizione al server: URL remoto: " + remoteService);
		boolean signed = false;
		try {
			UnoServerRM US = (UnoServerRM)Naming.lookup(remoteService);
			signed = US.SignIn(this.getPlayerName(), this.getPlayerPort());
		} catch(Exception e) {
			System.err.println("*[WARNING] non e' possibile effettuare l'iscrizione al server");
		}
		return signed;
	}
	
	/**
	 * Dato un oggetto UnoPlayerID ritorna l'oggetto UnoPlayer 
	 * corrispondente
	 * @throws NotBoundException 
	 * @throws MalformedURLException 
	 * */
	public static UnoPlayerRM playerIDToPlayer(UnoPlayerID playerId) {
		String address = playerId.getAddress();
		String remotePlayer = "rmi://" + address + "/unoPlayerRM";
		UnoPlayerRM UP = null;
		try {
			UP = (UnoPlayerRM)Naming.lookup(remotePlayer);
		} catch (Exception e) {
			System.err.println("*[WARNING] errore nel lookup del player. Metodo: playerIDToPlayer");
		}	
		return UP;
	}
	
	/**
	 * Ritorna il mio l'ID
	 * */
	public UnoPlayerID getMyPlayerID() { return new UnoPlayerID( UnoPlayer.getMyHostAddress() + ":" + this.getPlayerPort(), UnoPlayer.getMyHostname()); }
	
	/** 
	 * Carica l'RMI registry sulla porta con valore serverPort 
	 * */
	public void launchRMIRegistry() {
		try {
			 java.rmi.registry.LocateRegistry.createRegistry(this.getPlayerPort());
			 System.out.println("*[LOG] RMIRegistry pronto.");
		  } catch (Exception e) {
			 System.err.println("*[WARNING] ERRORE: eccezione durante il caricamento del RMIRegistry.");
			 e.printStackTrace();
		  }			
	}
	
	
	/** 
	 * Effettua il bind dell'oggetto per l'invocazione remota dei metodi
	 * */
	public void BindService() throws RemoteException, MalformedURLException, UnknownHostException {
		// compongo l'url del servizio 
		String objectURL = "rmi://" + UnoPlayer.getMyHostAddress() + ":" + this.getPlayerPort() + "/unoPlayerRM";
		System.out.println("* url: " + objectURL);
		Naming.rebind(objectURL, this);	
		System.out.println("*[LOG] bind dell'oggetto remoto effettuato.");
	}
	
	/**
	 * Ritorna l'hostname della macchina su cui sta girando l'applicazione
	 * */
	public static String getMyHostname() {
		String hostname = null;
		try {		
			InetAddress inet = InetAddress.getLocalHost();
			hostname = inet.getHostName();
		} catch(UnknownHostException e) {
			System.err.println("*[WARNING] retrieve dell'hostname locale fallito");
		} 
		return hostname;
	}
	
	/**
	 * Ritorna l'IP della macchina su cui sta girando l'applicazione
	 * */
	public static String getMyHostAddress() {
		String address = null;
		try {		
			InetAddress inet = InetAddress.getLocalHost();
			address = inet.getHostAddress();
		} catch(UnknownHostException e) {
			System.err.println("*[WARNING] retrieve dell'indirizzo IP locale fallito");
			e.printStackTrace();
		} 
		return address;
	}
	
	/**
	 * Lancia l'applicazione client
	 * 
	 * @param gui riferimento all'oggetto che gestisce la grafica
	 * */
	public static UnoPlayer start(Main gui) {
		System.out.println("*[LOG] avvio il client...");
		UnoPlayer localPeer = null;
		try {			
			// istanzio l'oggetto Player, con il nome del giocare (ID?) e' la porta del RMI registry
			// e l'oggetto Main che lo rappresenta graficamente
			localPeer = new UnoPlayer(UnoPlayer.getMyHostname(), gui);
			// lancio l RMI registry
			localPeer.launchRMIRegistry();			
			// effetua il bind dei servizi (metodi remoti)
			localPeer.BindService();
			// effettua l'iscrizione al server con indirizzo args[0]
			if (localPeer.SignMeIn()) {
				System.out.println("*[LOG] iscrizione al server effettuata.");
			} else {
				// se l'iscrizione sul server non e andata a buon fine, 
				// termino lanciando un eccezione
				System.err.println("*[WARNING] iscrizione al server fallita.");
				throw new Exception();
			}
			// attendo di iniziare a giocare...
			localPeer.getThreadFactory().createStartToPlayThread().start();			
		} catch(Exception e) {
			System.err.println("*[ERROR] Generata eccezione nel tentativo di lanciare il client");
		}
		// restituisco il riferimento del giocatore creato
		return localPeer;
	}
}